home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Libris Britannia 4
/
science library(b).zip
/
science library(b)
/
PROGRAMM
/
PASCAL
/
1093.ZIP
/
MUSIC.ARC
/
MUSIC.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1988-01-03
|
11KB
|
291 lines
unit Music;
{
MUSIC.PAS allows you play music on IBM PC or compatible using the same
set of commands that you would use with BASICA's "PLAY" command.
The original module was written and uploaded by Gregory Arakelian
(74017,223) 703-435-7137. The code was unitized for Turbo Pascal
4.0 by Ted Lassagne (70325,206). Code was added to handle dotted
notes. Some error checking was added, and minor corrections and
optimizations were made.
}
{=======================================================================}
interface
uses CRT;
Procedure Play (TuneString:string);
{Play interprets a string very similar to that used with the PLAY
verb in BASICA. The two major exceptions are that the "N" order
is not interpreted and that variables cannot appear in the string.
The string characters are interpreted as follows:
A .. G The musical notes A thru G. A note may be followed
by an accidental ('#' or '+' for sharp and '-' for
flat.) Additionally, a note (With optional sharp or
flat) may also be followed by a number denoting the
note length (1 for a whole note thru 64 for a 64th
note.) The note, with optional accidental and
length, may also be followed by one or more dots
("."), each of which extends the note by one half
of its existing value. For example, two dots produce
a length of 9/4 the original value, and three dots
a length of 27/8 the original value.
Ln Specifies the default length of the notes following
("n" must be 1 for a whole note thru 64 for a 64th
note.) The initial default value is 4 (quarter note.)
Mz Specifies the fraction of the note length that the
note is actually sounding. "z" is one of the letters
"S", "N", or "L", which have these meanings:
MS Music staccato (3/4 of note length)
MN Music normal (7/8 of note length)
ML Music legato (all of note length)
On Specifies the octave in which the notes following
are to be played (0 thru 7). The initial default
octave is 3, which is the octave which begins at
middle C.
Pn Specifies that no sound is to be made for an
interval. "n" (optional) is the note length (1
for a whole note thru 64 for a 64th note.) If "n"
is omitted, the current default note length is used.
One or more dots may follow, each of which extends
the rest by one half of its existing value.
Tn Specifies the tempo in beats per minute (32 thru
255.) The initial default value is 120.
Note: The playing may be interrupted at any time by pressing
Control-Break or Control-C. This terminates the program and
returns control to the operating system. If you want to
change this, the keyboard checking code immediately follows
the note playing code.
}
{=======================================================================}
implementation
Const
SharpOffset = 60;
Var
PitchArray : Array[1..120] Of Integer;
{The first 56 entries in PitchArray are frequencies for
the notes A..G in seven octaves. Entries 60 thru 115
are frequencies for the sharps of the notes in the
first 56 entries.}
BaseOctave : Integer;
Octave : Integer;
GenNoteType: Integer;
Tempo : Integer;
PlayFrac : Byte;
{PlayInit sets default values for octave, note length, tempo, and
note length modifier. It sets up the array of frequencies for the
notes.}
Procedure PlayInit;
Const
NextFreq = 1.05946309436;
Var
RealFreq : Array[1..7] Of Real;
BaseFreq : Real;
J,K : Integer;
Begin
{Set up default values}
BaseOctave := 0;
Octave := 3; {Third octave - starts with middle C}
GenNoteType := 4; {Quarter note}
Tempo := 120; {120 beats per minute}
PlayFrac := 7; {Normal - note plays for 7/8 of time}
{Set up frequency array}
BaseFreq := 27.5; {"A" four octaves below A-440}
For J := 0 To 7 Do
Begin
RealFreq[1] := BaseFreq;
RealFreq[2] := RealFreq[1]*NextFreq*NextFreq;
RealFreq[3] := RealFreq[2]*NextFreq;
RealFreq[4] := RealFreq[3]*NextFreq*NextFreq;
RealFreq[5] := RealFreq[4]*NextFreq*NextFreq;
RealFreq[6] := RealFreq[5]*NextFreq;
RealFreq[7] := RealFreq[6]*NextFreq*NextFreq;
BaseFreq := BaseFreq * 2; {next octave}
For K := 1 to 7 Do
Begin
PitchArray[J*7+K] := Round(RealFreq[K]);
PitchArray[J*7+K+SharpOffset] := Round(RealFreq[K]*NextFreq);
End;
End;
End;
{Play interprets the passed string and plays the specified notes for
the specified time periods. The orders in the string are interpreted
as outlined in the interface section above.}
Procedure Play (TuneString:string);
Var PlayTime,IdleTime,DotTime,NoteTime : Integer;
NoteType,PitchIndex,Position,Number : Integer;
Code,TuneStrLen : Integer;
Character : Char;
Procedure NVal(Pos:integer; var v, code: integer);
{Extracts a numeric value "v" from the tune string starting at
the index Pos. The returned value in "code" is the number of
digits scanned plus one.}
var posn:integer;
begin
v := 0;
posn := Pos;
while (posn <= TuneStrLen) and
(TuneString[posn] in ['0'..'9']) do begin
v := v*10 + ord(TuneString[posn]) - ord ('0');
posn := posn + 1;
end;
code := posn - Pos + 1;
end {NVal};
Procedure CheckDots;
{Checks for dots after note or pause. Each dot increases note
or rest length by half.}
begin
while (Position <= TuneStrLen) and
(TuneString[Position] = '.') do begin
DotTime := DotTime + DotTime div 2;
inc(Position)
end;
end {CheckDots};
Begin {Play subroutine}
CheckBreak := false;
TuneStrLen := length(TuneString);
Position := 1;
Repeat
NoteType := GenNoteType;
DotTime := 1000;
Character := upcase(TuneString[Position]);
Case Character Of
'A'..'G' : Begin
PitchIndex := (ord(Character)-64)+Octave*7;
If (Character='A') or (Character='B') Then
PitchIndex := PitchIndex + 7; {next octave}
inc(Position);
{Check for sharp or flat}
if Position <= TuneStrLen then
case TuneString[Position] of
'#','+': begin
PitchIndex := PitchIndex+SharpOffset;
inc(Position);
end;
'-': begin
PitchIndex := PitchIndex+SharpOffset - 1;
inc(Position);
end;
End;
{Check for length following note}
if (Position <= TuneStrLen) and
(TuneString[Position] in ['0'..'9']) then begin
NVal(Position,NoteType,Code);
inc(Position, Code - 1)
end;
{Check for dots after note}
CheckDots;
{Play the note}
NoteTime := Round(DotTime/Tempo/NoteType*240);
PlayTime := Round(NoteTime*PlayFrac/8);
IdleTime := NoteTime-PlayTime;
Sound(PitchArray[PitchIndex]);
Delay(PlayTime);
if IdleTime <> 0 then begin
NoSound;
Delay(IdleTime)
end;
{Check for Ctl-Break pressed}
if keypressed and (ReadKey = ^C) then begin
NoSound;
halt
end;
End;
'L' : {Note length (1 thru 64). "1" signifies a
whole note and "64" a 64th note.}
Begin
NVal (Position+1,GenNoteType,Code);
if (GenNoteType < 1) or (GenNoteType > 64) then
GenNoteType := 4;
inc(Position, Code);
End;
'M' : {Note length modifier - "S" for staccato,
"L" for legato, or "N" for normal.}
Begin
if Position < TuneStrLen then begin
Case upcase(TuneString[Position+1]) Of
'S' : PlayFrac := 6;
'N' : PlayFrac := 7;
'L' : PlayFrac := 8;
End;
inc(Position, 2);
end;
End;
'O' : {Octave specification (0 thru 7)}
Begin
NVal (Position+1,Octave,Code);
Octave := Octave+BaseOctave;
if Octave > 7 then Octave := 3;
inc(Position, Code);
End;
'P' : {Pause (rest) followed by optional value of
1 thru 64, with "1" signifying a whole rest
and "64" a 64th rest.}
Begin
NoSound;
NVal (Position+1,NoteType,Code);
if (NoteType < 1) or (NoteType > 64) then
NoteType := GenNoteType;
inc(Position, Code);
CheckDots;
IdleTime := DotTime Div Tempo * (240 Div NoteType);
Delay (IdleTime);
End;
'T' : {Tempo - number of beats per minute (32 - 255)}
Begin
NVal (Position+1,Tempo,Code);
if (Tempo < 32) or (Tempo > 255) then
Tempo := 120;
inc(Position, Code);
End;
Else inc(Position); {Ignore spurious characters}
End;
Until Position > TuneStrLen;
NoSound;
End {Play};
Begin {Initialization}
PlayInit;
End.